package org.pvt.model;

import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import static org.pvt.model.db.DBUtils.getWritableDB;

/**
 * @author Pavel Talanov
 */
public abstract class Model {

    @NotNull
    public static String referenceById(@NotNull Model model) {
        return model.getTableName() + "_id";
    }

    protected final class Field {
        @NotNull
        public final String name;
        @NotNull
        public final String type;
        @Nullable
        public final Model referencedModel;
        @Nullable
        public final Behaviour onDelete;

        public Field(@NotNull String name, @NotNull String type) {
            this(name, type, null, null);
        }

        public Field(@NotNull Model model, @NotNull Behaviour onDelete) {
            this(referenceById(model), "INTEGER", model, onDelete);
        }

        private Field(@NotNull String name, @NotNull String type, @Nullable Model model, @Nullable Behaviour onDelete) {
            this.type = type;
            this.name = name;
            this.referencedModel = model;
            this.onDelete = onDelete;
        }

        private boolean isForeignKey() {
            return referencedModel != null;
        }
    }

    protected enum Behaviour {
        CASCADE("CASCADE"),
        RESTRICT("RESTRICT");

        @NotNull
        public final String repr;

        Behaviour(@NotNull String repr) {
            this.repr = repr;
        }
    }

    /*package*/ Model() {
    }

    @NotNull
    public static String ID = "_id";

    @NotNull
    public abstract String getTableName();

    @NotNull
    public final String[] getFieldNames() {
        Set<String> fieldNames = new HashSet<String>();
        for (Field field : getFields()) {
            fieldNames.add(field.name);
        }
        return fieldNames.toArray(new String[fieldNames.size()]);
    }

    @NotNull
    protected Set<Field> getFields() {
        return Collections.singleton(new Field(ID, "INTEGER PRIMARY KEY"));
    }

    public final void create(@NotNull ContentValues values, @NotNull Context context) {
        SQLiteDatabase writableDB = getWritableDB(context);
        writableDB.insert(getTableName(), null, values);
    }

    public final void update(long id, @NotNull ContentValues values, @NotNull Context context) {
        SQLiteDatabase writableDB = getWritableDB(context);
        writableDB.update(getTableName(), values, ID + " = " + id, null);
    }

    public final void delete(long id, @NotNull Context context) {
        SQLiteDatabase writableDB = getWritableDB(context);
        writableDB.delete(getTableName(), ID + " = " + id, null);
    }

    @NotNull
    public final String getCreationSQL() {
        StringBuilder sql = new StringBuilder();
        sql.append("CREATE TABLE " + getTableName() + "\n(");
        Field[] fields = fieldsAsArray();
        int i = 0;
        while (i < fields.length) {
            Field field = fields[i];
            boolean isLast = i == fields.length - 1;
            sql.append(sqlForField(field, isLast));
            ++i;
        }
        sql.append(");");
        return sql.toString();
    }

    @NotNull
    private String sqlForField(@NotNull Field field, boolean isLast) {
        StringBuilder sql = new StringBuilder();
        sql.append(field.name + " " + field.type);
        if (!isLast && !field.isForeignKey()) {
            sql.append(",");
        }
        sql.append("\n");
        if (!field.isForeignKey()) {
            return sql.toString();
        }
        sql.append(" REFERENCES "
                + field.referencedModel.getTableName() + "(" + ID + ") ON DELETE " + field.onDelete);

        if (!isLast) {
            sql.append(",");
        }
        sql.append("\n");
        return sql.toString();
    }

    private Field[] fieldsAsArray() {
        Set<Field> fields = getFields();
        return fields.toArray(new Field[fields.size()]);
    }
}
